/*
 * File     : internal_platform_PlatformUtilities_windows.c
 * Created  : 28 Apr 2011
 *
 * Copyright © 2011 Matthew Wilson (mj. {my-surname} .uk {at} gmail.com)
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "../common.h"

#define UNICODE
#define _UNICODE
#include <windows.h>

/*
 * Class:     com_googlecode_dni_internal_platform_PlatformUtilities
 * Method:    getPlatformGroup
 * Signature: ()Lcom/googlecode/dni/platform/PlatformGroup;
 */
JNIEXPORT jobject JNICALL Java_com_googlecode_dni_internal_platform_PlatformUtilities_getPlatformGroup(
    JNIEnv* env,
    jclass clazz )
{
    jclass platformGroupClass;
    jfieldID field;

    platformGroupClass = (*env)->FindClass( env, "com/googlecode/dni/platform/PlatformGroup" );
    if ( platformGroupClass == NULL )
    {
       return NULL;
    }
    field = (*env)->GetStaticFieldID( env, platformGroupClass, "WINDOWS", "Lcom/googlecode/dni/platform/PlatformGroup;" );
    if ( field == NULL )
    {
       return NULL;
    }
    return (*env)->GetStaticObjectField( env, platformGroupClass, field );
}

/*
 * Class:     com_googlecode_dni_internal_platform_PlatformUtilities
 * Method:    getPlatformType
 * Signature: ()Lcom/googlecode/dni/platform/PlatformType;
 */
JNIEXPORT jobject JNICALL Java_com_googlecode_dni_internal_platform_PlatformUtilities_getPlatformType(
    JNIEnv* env,
    jclass clazz )
{
    char* name;
    jclass platformTypeClass;
    jfieldID field;

#if defined(_M_IX86)
    name = "X86";
#elif defined(_M_X64)
    name = "X64";
#else
#error Unknown platform
#endif

    platformTypeClass = (*env)->FindClass( env, "com/googlecode/dni/platform/WindowsPlatformType" );
    if ( platformTypeClass == NULL )
    {
       return NULL;
    }
    field = (*env)->GetStaticFieldID( env, platformTypeClass, name, "Lcom/googlecode/dni/platform/WindowsPlatformType;" );
    if ( field == NULL )
    {
       return NULL;
    }
    return (*env)->GetStaticObjectField( env, platformTypeClass, field );
}

/*
 * Class:     com_googlecode_dni_internal_platform_PlatformUtilities
 * Method:    getNativeLocale
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jobject JNICALL Java_com_googlecode_dni_internal_platform_PlatformUtilities_getNativeLocale(
    JNIEnv* env,
    jclass clazz )
{
    // not implemented
    return NULL;
}

/*
 * Class:     com_googlecode_dni_internal_platform_PlatformUtilities
 * Method:    getPageSize
 * Signature: ()I
 */
JNIEXPORT jint JNICALL Java_com_googlecode_dni_internal_platform_PlatformUtilities_getPageSize(
    JNIEnv* env,
    jclass clazz )
{
    SYSTEM_INFO systemInfo;
    GetSystemInfo( &systemInfo );

    return systemInfo.dwPageSize;
}

static int protFlags( jint flags )
{
    int prot;
    switch ( flags )
    {
        case 1:
            prot = PAGE_EXECUTE_READ;
            break;

        case 2:
            prot = PAGE_READWRITE;
            break;

        case 3:
            prot = PAGE_EXECUTE_READWRITE;
            break;

        default:
            prot = PAGE_READONLY;
            break;
    }
    return prot;
}

/*
 * Class:     com_googlecode_dni_internal_platform_PlatformUtilities
 * Method:    allocatePages
 * Signature: (II)J
 */
JNIEXPORT jlong JNICALL Java_com_googlecode_dni_internal_platform_PlatformUtilities_allocatePages(
    JNIEnv* env,
    jclass clazz,
    jint size,
    jint flags )
{
    void* ptr;
    int prot = protFlags( flags );

    ptr = VirtualAlloc( NULL, size, MEM_COMMIT, prot );

    if ( ptr == NULL )
    {
        return 0;
    }

    return POINTER_TO_JLONG( ptr );
}

/*
 * Class:     com_googlecode_dni_internal_platform_PlatformUtilities
 * Method:    protectPages
 * Signature: (JII)I
 */
JNIEXPORT jint JNICALL Java_com_googlecode_dni_internal_platform_PlatformUtilities_protectPages(
    JNIEnv* env,
    jclass clazz,
    jlong address,
    jint size,
    jint flags )
{
    void* ptr = JLONG_TO_POINTER( address );
    int prot = protFlags( flags );
    int oldProt;
    int result;

    result = VirtualProtect( ptr, (size_t) size, prot, &oldProt );

    // yes, zero means error (!)
    if ( result == 0 )
    {
        return GetLastError();
    }
    
    if ( prot & 1 )
    {
        // executable, so please clear cache
        FlushInstructionCache( GetCurrentProcess(), NULL, 0 );
    }

    return 0;
}

/*
 * Class:     com_googlecode_dni_internal_platform_PlatformUtilities
 * Method:    unallocatePages
 * Signature: (JI)I
 */
JNIEXPORT jint JNICALL Java_com_googlecode_dni_internal_platform_PlatformUtilities_unallocatePages(
    JNIEnv* env,
    jclass clazz,
    jlong pointer,
    jint size )
{
    int result;
    
    result = VirtualFree( JLONG_TO_POINTER( pointer ), 0, MEM_RELEASE );
    
    // yes, zero means error (!)
    if ( result == 0 )
    {
        return GetLastError();
    }
    
    return 0; 
}

#include <windows.h>
JNIEXPORT double slets()
{
	SetLastError( 42 );
	return 123;
}
